/*************************************************************************
 *                     COPYRIGHT NOTICE
 *            Copyright 2024 Horizon Robotics, Inc.
 *                   All rights reserved.
 *************************************************************************/
#include <iostream>
#include <stdio.h>
#include <stdexcept>
#include <syscall.h>
#include "gpu_sample_utils.hpp"

OptionParser::OptionParser(int32_t argc, char** argv)
	: m_sampleBinpath(BIN_PATH_STR_MAX_LENGTH, '\0')
	, m_totalFrames(0)
	, m_app_cmd(argv[0])
	, m_benchmark_thread_run(false)
	, m_count(0)
	, m_exit_num(EXIT_SUCCESS)
{
	int32_t length_back = 0;
	int32_t path_end_index = 0;

	main_thread_id = syscall(SYS_gettid);

	length_back = readlink("/proc/self/exe", const_cast<char *>(m_sampleBinpath.c_str()), BIN_PATH_STR_LENGTH);
	if (length_back >= BIN_PATH_STR_LENGTH) {
		throw std::invalid_argument("Parse sample bin path error");
	}

	path_end_index = m_sampleBinpath.find_last_of(sample_path_sufix);
	if (path_end_index == std::string::npos) {
		throw std::invalid_argument("Parse sample bin path error");
	}
	m_sampleBinpath = m_sampleBinpath.substr(0, path_end_index + BIN_PATH_SUFIX_LENGTH);

	for (int32_t i = BIN_PATH_INDEX + 1; i < argc; ++i) {
		for (int32_t j = 0; j < MAX_OPTIONS_NUM; ++j) {
			get_option(j, std::string(argv[i]));
		}
	}

	check_option();
}

OptionParser::~OptionParser()
{
	m_benchmark_thread_run = false;

	if (m_benchmark_thread.joinable()) {
		m_benchmark_thread.join();
	}

	if (m_options[BENCHMARK].val > 0 && m_options[MAX_FPS].val > 0) {
		if (m_benchmark_fps_thread.joinable()) {
			m_benchmark_fps_thread.join();
		}

		pthread_cond_destroy(&m_cond);
		pthread_mutex_destroy(&m_mutex);
	}

	if (m_exit_num != EXIT_SUCCESS) {
		exit(m_exit_num);
	}
}

/**
 * @brief 解析用户输入的选项并提取这个选项的值
 */
void OptionParser::get_option(int32_t option_index, std::string arg)
{
	int32_t pos = -1;
	const int32_t space_str_length = 1;

	if ((pos = arg.find(m_options[option_index].option)) != -1) {
		if (m_options[option_index].has_arg) {
			try {
				m_options[option_index].val = std::stod(arg.substr(pos +
										       m_options[option_index].option.length()
										       + space_str_length));
			} catch(std::invalid_argument&) {
				print_usage();
				throw std::invalid_argument("Parse arguments error");
			}
		} else if (arg == m_options[HELP].option) {
			print_usage();
			exit(0);
		}
	}
}
void OptionParser::print_usage()
{
	for (auto option : m_options) {
		std::cout << option.option
			  << ": "
			  << option.description
			  << std::endl;
	}
}

void OptionParser::check_option()
{
	if (m_options[BENCHMARK].val < 0) {
		throw std::invalid_argument("benchmark value should be a positive number or 0");
	}
	if (m_options[DUMP_IMAGE].val != 0 && m_options[WINSYS_TYPE].val == (int32_t)WinSysType::WAYLAND) {
		throw std::invalid_argument("Dump image is only supported in gles: gbm, vulkan: no_winsys");
	}
	if (m_options[WINSYS_OUT_TYPE].val < 0 &&
	    m_options[WINSYS_OUT_TYPE].val >= (int32_t)WinSysOutType::WIN_OUT_TYPE_NUM) {
		throw std::invalid_argument("A invalid winsys out type is chosed.");
	}

	// when check_option success and the test mode is benchmark, create an thread to monitor process info
	if (m_options[BENCHMARK].val > 0) {
		prepare_benchmark();
	}
}

void OptionParser::prepare_benchmark()
{
	int32_t ret;
	pthread_condattr_t attr;

	m_benchmark_thread_run = true;

	if (m_options[MAX_FPS].val > 0) {
		ret = pthread_condattr_init(&attr);
		if (ret != 0) {
			throw std::system_error();
		}

		ret = pthread_condattr_setclock(&attr, CLOCK_MONOTONIC);
		if (ret != 0) {
			throw std::system_error();
		}

		ret = pthread_cond_init(&m_cond, &attr);
		if (ret != 0) {
			ret = pthread_condattr_destroy(&attr);
		}

		ret = pthread_condattr_destroy(&attr);
		if (ret != 0) {
			throw std::system_error();
		}

		ret = pthread_mutex_init(&m_mutex, nullptr);
		if (ret != 0) {
			throw std::system_error();
		}

		m_benchmark_fps_thread = std::thread(&OptionParser::benchmark_fps_limit_thread, this);
	}

	m_benchmark_thread = std::thread(&OptionParser::benchmark_thread, this);
}

void OptionParser::benchmark_fps_limit_thread()
{
	int32_t one_thousand_milliseconds = 1000;

	while (m_benchmark_thread_run) {

		std::this_thread::sleep_for(std::chrono::milliseconds(static_cast<int32_t>(one_thousand_milliseconds / m_options[MAX_FPS].val)));

		pthread_mutex_lock(&m_mutex);

		++m_count;

		pthread_cond_signal(&m_cond);

		pthread_mutex_unlock(&m_mutex);
	}
}

void OptionParser::benchmark_thread()
{
	struct Benchmark_info {
		enum Benchmark_info_index {
			CPU,
			FPS,
			TOTAL_FRAMES,
			TOTAL_INFO
		};
		std::string description;
		double val;
	} benchmark_info[Benchmark_info::TOTAL_INFO] = {
		{
			"CPU(\%)",
			0.0,
		},
		{
			"FPS",
			0.0,
		},
		{
			"Total frames",
			0.0
		}
	};
	size_t max_sys_command_str_len = 300;
	std::string sys_command = "top -c -b -n 1 | grep " + std::to_string(main_thread_id) + " | awk '{print $9}'";
	std::string sys_command_ret(max_sys_command_str_len, '\0');
	int32_t loop_times = 0;
	const int32_t thread_sleep_time_for_top = 2; // 2 seceonds
	std::ios_base::fmtflags oldfmtflags;

	while (m_benchmark_thread_run) {
		// get cpu info
		++loop_times;
		do_sys_command(sys_command.c_str(), const_cast<char *>(sys_command_ret.c_str()), max_sys_command_str_len);
		std::this_thread::sleep_for(std::chrono::seconds(thread_sleep_time_for_top));

		try {
			benchmark_info[Benchmark_info::CPU].val += std::stof(sys_command_ret);
		} catch (const std::exception &bex){
			std::cout << "Caught exeption:"
				  << bex.what()
				  << __FILE__
				  << __LINE__
				  << __func__
				  << std::endl;
		}
	}

	benchmark_info[Benchmark_info::CPU].val /= loop_times;
	benchmark_info[Benchmark_info::FPS].val = (double)m_totalFrames / m_options[BENCHMARK].val;
	benchmark_info[Benchmark_info::TOTAL_FRAMES].val = (double)m_totalFrames;

	oldfmtflags = std::cout.setf(std::ios_base::fixed | std::ios_base::left, std::ios_base::floatfield);
	std::cout << "Process info:" + m_app_cmd << std::endl;
	for (auto info : benchmark_info) {
		std::cout.width(info_str_width);
		std::cout << std::left << info.description << ":";
		std::cout.setf(std::ios_base::fixed | std::ios_base::right, std::ios_base::floatfield);
		std::cout.width(info_str_width);
		std::cout.precision(6);
		std::cout << std::right << info.val << std::endl;
		std::cout.setf(std::ios_base::fixed | std::ios_base::left, std::ios_base::floatfield);
	}

	std::cout.setf(std::ios_base::adjustfield, oldfmtflags);
	std::cout << std::endl;

	if (m_options[DESIRED_FPS].val > benchmark_info[Benchmark_info::FPS].val) {
		std::cout << std::to_string(benchmark_info[Benchmark_info::FPS].val)
			  << "(actual fps) "
			  << " < "
			  << std::to_string(m_options[DESIRED_FPS].val)
			  << "(desired fps) "
			  << std::endl;
		m_exit_num = EXIT_FAILURE;
	}

	if (m_options[DESIRED_CPU_COST].val < benchmark_info[Benchmark_info::CPU].val) {
		std::cout << std::to_string(benchmark_info[Benchmark_info::CPU].val)
			  << "(actual cpu cost) "
			  << " > "
			  << std::to_string(m_options[DESIRED_CPU_COST].val)
			  << "(desired cpu cost) "
			  << std::endl;
		m_exit_num = EXIT_FAILURE;
	}
}

void OptionParser::wait()
{
	if (m_options[BENCHMARK].val > 0 && m_options[MAX_FPS].val > 0) {
		pthread_mutex_lock(&m_mutex);

		if (m_count <= 0) {
			pthread_cond_wait(&m_cond, &m_mutex);
		}
		--m_count;
		pthread_mutex_unlock(&m_mutex);
	}
}

resource_monitor_thread::resource_monitor_thread(bool start_on_create, int32_t thread_pid)
	: m_thread_pid(thread_pid)
	, m_monitor_thread_run(true)
{
	if (m_thread_pid == INVALID_THREAD_ID) {
		m_thread_pid = syscall(SYS_gettid);
	}

	if (start_on_create) {
		run();
	}
}

resource_monitor_thread::~resource_monitor_thread()
{
	m_monitor_thread_run = false;

	if (m_monitor_thread.joinable()) {
		m_monitor_thread.join();
	}
}

void resource_monitor_thread::run()
{
	m_monitor_thread = std::thread(&resource_monitor_thread::monitor_func, this);
}

void resource_monitor_thread::monitor_func(void)
{
	int32_t loop_times = 0;
	size_t max_sys_command_str_len = 300;
	std::string sys_command = "top -H -b -n 1 -p " +
				  std::to_string(m_thread_pid) +
				  "| grep " +
				  std::to_string(m_thread_pid) +
				  " | awk '{print $9}'";
	std::string sys_command_ret(max_sys_command_str_len, '\0');
	const int32_t thread_sleep_time_for_top = 2; // 2 seceonds
	std::ios_base::fmtflags oldfmtflags;

	while (m_thread_pid >= 0 && m_monitor_thread_run) {
		// get cpu info
		++loop_times;
		do_sys_command(sys_command.c_str(), const_cast<char *>(sys_command_ret.c_str()), max_sys_command_str_len);
		std::this_thread::sleep_for(std::chrono::seconds(thread_sleep_time_for_top));

		try {
			monitor_info[monitor_info::CPU].val += std::stof(sys_command_ret);
		} catch (const std::bad_exception &bex){
			std::cout << "Caught bad exeption:"
				  << bex.what()
				  << sys_command_ret
				  << __FILE__
				  << __LINE__
				  << __func__
				  << std::endl;
			return;
		}
	}

	monitor_info[monitor_info::CPU].val /= loop_times;

	oldfmtflags = std::cout.setf(std::ios_base::fixed | std::ios_base::left, std::ios_base::floatfield);
	std::cout << "Thread(pid) " + std::to_string(m_thread_pid) + " info :" << std::endl;
	for (auto info : monitor_info) {
		std::cout.width(info_str_width);
		std::cout << std::left << info.description << ":";
		std::cout.setf(std::ios_base::fixed | std::ios_base::right, std::ios_base::floatfield);
		std::cout.width(info_str_width);
		std::cout.precision(6);
		std::cout << std::right << info.val << std::endl;
		std::cout.setf(std::ios_base::fixed | std::ios_base::left, std::ios_base::floatfield);
	}

	std::cout.setf(std::ios_base::adjustfield, oldfmtflags);
	std::cout << std::endl;
}

void do_sys_command(const char *sys_command, char * const buf, size_t &buf_size)
{
	FILE *stream = nullptr;

	memset(buf, '\0', sizeof(buf_size));

	stream = popen(sys_command, "r");
	buf_size = fread(buf, sizeof(char), buf_size,  stream);

	pclose(stream);
}

int32_t read_packed_file(const char *filename, uint8_t *addr0, uint32_t y_size)
{
	FILE *Fd = NULL;
	uint32_t file_read_size = 0;

	if (filename == NULL || addr0 == NULL || y_size == 0) {
		printf("invalid parameter\n");
		return -1;
	}

	Fd = fopen(filename, "r");

	if (Fd == NULL) {
		printf("ERR(%s):open(%s) fail\n", __func__, filename);
		return -1;
	}

	file_read_size = fread(addr0, 1, y_size, Fd);
	if (file_read_size != y_size) {
		fclose(Fd);
		printf("read bin(%s) to addr fail, file_read_size: %d, y_size: %d #1\n",
			filename, file_read_size, y_size);
		return -1;
	}

	fflush(Fd);

	if (Fd)
		fclose(Fd);

	return 0;
}

int32_t read_yuv420sp_file(const char *filename, uint8_t *addr0, uint8_t *addr1, uint32_t y_size)
{
	FILE *Fd = NULL;

	if (filename == NULL || addr0 == NULL || y_size == 0) {
		printf("ERR(%s):null param.\n", __func__);
		return -1;
	}

	Fd = fopen(filename, "r");

	if (Fd == NULL) {
		printf("ERR(%s):open(%s) fail\n", __func__, filename);
		return -1;
	}

	if (fread(addr0, 1, y_size, Fd) != y_size) {
		fclose(Fd);
		printf("read bin(%s) to addr fail #1\n", filename);
		return -1;
	}

	if (fread(addr1, 1, y_size / 2, Fd) != y_size / 2) {
		fclose(Fd);
		printf("read bin(%s) to addr fail #2\n", filename);
		return -1;
	}

	fflush(Fd);

	if (Fd)
		fclose(Fd);

	return 0;
}

int32_t disp_save_file(const char* filename, uint8_t** buf, uint64_t *size, uint32_t plane_num)
{
	FILE *fp = NULL;
	size_t written;
	uint32_t i;

	fp = fopen(filename, "wb");
	if (fp == NULL)
	{
		printf("open(%s) fail", filename);
		return -1;
	}

	for (i = 0; i < plane_num; i++)
	{
		written = fwrite(buf[i], 1, size[i], fp);
		if (written != size[i])
		{
			printf("Failed to write buf[%d] to file %s, written = %ld, size = %ld", i, filename, written, size[i]);
			fclose(fp);
			return -1;
		}
	}

	fflush(fp);
	fclose(fp);
	printf("Dumpfile(%s) successed\n", filename);
	return 0;
}

int32_t disp_save_file_mod(const char* filename, void* data, uint64_t *size)
{
	FILE* fp = nullptr;
	uint64_t written;
	
	fp = fopen(filename, "wb");
	if (fp == nullptr)
	{
		printf("open(%s) fail", filename);
		return -1;
	}

	written = fwrite(data, 1, *size, fp);
	if(written != *size)
	{
		printf("Failed to write data to file %s, written = %ld, size = %ld", filename, written, *size);
		return -1;
	}

	fflush(fp);
	fclose(fp);
	printf("Dumpfile(%s) successed\n", filename);

	// test output 
	std::cout << "dump file check:" << std::endl;
	fp = fopen(filename, "rb");
	if(fp == nullptr){
		printf("open(%s) fail", filename);
		return -1;
	}

	const int numBytesToRead = 16;
	char buffer[numBytesToRead];
	size_t byteRead = fread(buffer, 1, numBytesToRead, fp);
	if(byteRead != numBytesToRead){
		std::cerr << "Error reading file" << std::endl;
		return -1;
	}
	fclose(fp);

	for(int i = 0; i < numBytesToRead; i++){

		if(i % 4 == 0)
			std::cout << std::endl;

		printf(" %d ", (u_char)buffer[i]);
	}
	std::cout << std::endl;

	return 0;
}